// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
        const char *nonce, const char *nc, const char *cnonce,
        const char *qop, const char *response) {
    char ha2[32 + 1], expected_response[32 + 1];

    // Some of the parameters may be NULL
    if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
            qop == NULL || response == NULL) {
        return 0;
    }

    // NOTE(lsm): due to a bug in MSIE, we do not compare the URI
    // TODO(lsm): check for authentication timeout
    if (// strcmp(dig->uri, c->ouri) != 0 ||
            strlen(response) != 32
            // || now - strtoul(dig->nonce, NULL, 10) > 3600
       ) {
        return 0;
    }

    mg_md5(ha2, method, ":", uri, NULL);
    mg_md5(expected_response, ha1, ":", nonce, ":", nc,
            ":", cnonce, ":", qop, ":", ha2, NULL);

    return mg_strcasecmp(response, expected_response) == 0;
}

// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
    struct mg_context *ctx = conn->ctx;
    char name[PATH_MAX];
    const char *p, *e;
    struct mgstat st;
    FILE *fp;

    if (ctx->config[GLOBAL_PASSWORDS_FILE] != NULL) {
        // Use global passwords file
        fp =  mg_fopen(ctx->config[GLOBAL_PASSWORDS_FILE], "r");
        if (fp == NULL)
            cry(fc(ctx), "fopen(%s): %s",
                    ctx->config[GLOBAL_PASSWORDS_FILE], strerror(ERRNO));
    } else if (!mg_stat(path, &st) && st.is_directory) {
        (void) mg_snprintf(conn, name, sizeof(name), "%s%c%s",
                path, DIRSEP, PASSWORDS_FILE_NAME);
        fp = mg_fopen(name, "r");
    } else {
        // Try to find .htpasswd in requested directory.
        for (p = path, e = p + strlen(p) - 1; e > p; e--)
            if (IS_DIRSEP_CHAR(*e))
                break;
        (void) mg_snprintf(conn, name, sizeof(name), "%.*s%c%s",
                (int) (e - p), p, DIRSEP, PASSWORDS_FILE_NAME);
        fp = mg_fopen(name, "r");
    }

    return fp;
}

// Parsed Authorization header
struct ah {
    char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
};

static int parse_auth_header(struct mg_connection *conn, char *buf,
        size_t buf_size, struct ah *ah) {
    char *name, *value, *s;
    const char *auth_header;

    if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
            mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
        return 0;
    }

    // Make modifiable copy of the auth header
    (void) mg_strlcpy(buf, auth_header + 7, buf_size);

    s = buf;
    (void) memset(ah, 0, sizeof(*ah));

    // Parse authorization header
    for (;;) {
        // Gobble initial spaces
        while (isspace(* (unsigned char *) s)) {
            s++;
        }
        name = skip_quoted(&s, "=", " ", 0);
        /* Value is either quote-delimited, or ends at first comma or space. */
        if (s[0] == '\"') {
            s++;
            value = skip_quoted(&s, "\"", " ", '\\');
            if (s[0] == ',') {
                s++;
            }
        } else {
            value = skip_quoted(&s, ", ", " ", 0);  // IE uses commas, FF uses spaces
        }
        if (*name == '\0') {
            break;
        }

        if (!strcmp(name, "username")) {
            ah->user = value;
        } else if (!strcmp(name, "cnonce")) {
            ah->cnonce = value;
        } else if (!strcmp(name, "response")) {
            ah->response = value;
        } else if (!strcmp(name, "uri")) {
            ah->uri = value;
        } else if (!strcmp(name, "qop")) {
            ah->qop = value;
        } else if (!strcmp(name, "nc")) {
            ah->nc = value;
        } else if (!strcmp(name, "nonce")) {
            ah->nonce = value;
        }
    }

    // CGI needs it as REMOTE_USER
    if (ah->user != NULL) {
        conn->request_info.remote_user = mg_strdup(ah->user);
    } else {
        return 0;
    }

    return 1;
}

// Authorize against the opened passwords file. Return 1 if authorized.
static int authorize(struct mg_connection *conn, FILE *fp) {
    struct ah ah;
    char line[256], f_user[256], ha1[256], f_domain[256], buf[BUFSIZ];

    if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
        return 0;
    }

    // Loop over passwords file
    while (fgets(line, sizeof(line), fp) != NULL) {
        if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
            continue;
        }

        if (!strcmp(ah.user, f_user) &&
                !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
            return check_password(
                    conn->request_info.request_method,
                    ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop,
                    ah.response);
    }

    return 0;
}

// Return 1 if request is authorised, 0 otherwise.
static int check_authorization(struct mg_connection *conn, const char *path) {
    FILE *fp;
    char fname[PATH_MAX];
    struct vec uri_vec, filename_vec;
    const char *list;
    int authorized;

    fp = NULL;
    authorized = 1;

    list = conn->ctx->config[PROTECT_URI];
    while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
        if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
            (void) mg_snprintf(conn, fname, sizeof(fname), "%.*s",
                    filename_vec.len, filename_vec.ptr);
            if ((fp = mg_fopen(fname, "r")) == NULL) {
                cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno));
            }
            break;
        }
    }

    if (fp == NULL) {
        fp = open_auth_file(conn, path);
    }

    if (fp != NULL) {
        authorized = authorize(conn, fp);
        (void) fclose(fp);
    }

    return authorized;
}

static void send_authorization_request(struct mg_connection *conn) {
    conn->request_info.status_code = 401;
    (void) mg_printf(conn,
            "HTTP/1.1 401 Unauthorized\r\n"
            "WWW-Authenticate: Digest qop=\"auth\", "
            "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
            conn->ctx->config[AUTHENTICATION_DOMAIN],
            (unsigned long) time(NULL));
}

static int is_authorized_for_put(struct mg_connection *conn) {
    FILE *fp;
    int ret = 0;

    fp = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ? NULL :
        mg_fopen(conn->ctx->config[PUT_DELETE_PASSWORDS_FILE], "r");

    if (fp != NULL) {
        ret = authorize(conn, fp);
        (void) fclose(fp);
    }

    return ret;
}

int mg_modify_passwords_file(const char *fname, const char *domain,
        const char *user, const char *pass) {
    int found;
    char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
    FILE *fp, *fp2;

    found = 0;
    fp = fp2 = NULL;

    // Regard empty password as no password - remove user record.
    if (pass[0] == '\0') {
        pass = NULL;
    }

    (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);

    // Create the file if does not exist
    if ((fp = mg_fopen(fname, "a+")) != NULL) {
        (void) fclose(fp);
    }

    // Open the given file and temporary file
    if ((fp = mg_fopen(fname, "r")) == NULL) {
        return 0;
    } else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) {
        fclose(fp);
        return 0;
    }

    // Copy the stuff to temporary file
    while (fgets(line, sizeof(line), fp) != NULL) {
        if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
            continue;
        }

        if (!strcmp(u, user) && !strcmp(d, domain)) {
            found++;
            if (pass != NULL) {
                mg_md5(ha1, user, ":", domain, ":", pass, NULL);
                fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
            }
        } else {
            (void) fprintf(fp2, "%s", line);
        }
    }

    // If new user, just add it
    if (!found && pass != NULL) {
        mg_md5(ha1, user, ":", domain, ":", pass, NULL);
        (void) fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
    }

    // Close files
    (void) fclose(fp);
    (void) fclose(fp2);

    // Put the temp file in place of real file
    (void) mg_remove(fname);
    (void) mg_rename(tmp, fname);

    return 1;
}

struct de {
    struct mg_connection *conn;
    char *file_name;
    struct mgstat st;
};

static void url_encode(const char *src, char *dst, size_t dst_len) {
    static const char *dont_escape = "._-$,;~()";
    static const char *hex = "0123456789abcdef";
    const char *end = dst + dst_len - 1;

    for (; *src != '\0' && dst < end; src++, dst++) {
        if (isalnum(*(const unsigned char *) src) ||
                strchr(dont_escape, * (const unsigned char *) src) != NULL) {
            *dst = *src;
        } else if (dst + 2 < end) {
            dst[0] = '%';
            dst[1] = hex[(* (const unsigned char *) src) >> 4];
            dst[2] = hex[(* (const unsigned char *) src) & 0xf];
            dst += 2;
        }
    }

    *dst = '\0';
}

static void print_dir_entry(struct de *de) {
    char size[64], mod[64], href[PATH_MAX];

    if (de->st.is_directory) {
        (void) mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]");
    } else {
        // We use (signed) cast below because MSVC 6 compiler cannot
        // convert unsigned __int64 to double. Sigh.
        if (de->st.size < 1024) {
            (void) mg_snprintf(de->conn, size, sizeof(size),
                    "%lu", (unsigned long) de->st.size);
        } else if (de->st.size < 1024 * 1024) {
            (void) mg_snprintf(de->conn, size, sizeof(size),
                    "%.1fk", (double) de->st.size / 1024.0);
        } else if (de->st.size < 1024 * 1024 * 1024) {
            (void) mg_snprintf(de->conn, size, sizeof(size),
                    "%.1fM", (double) de->st.size / 1048576);
        } else {
            (void) mg_snprintf(de->conn, size, sizeof(size),
                    "%.1fG", (double) de->st.size / 1073741824);
        }
    }
    (void) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.mtime));
    url_encode(de->file_name, href, sizeof(href));
    de->conn->num_bytes_sent += mg_printf(de->conn,
            "<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
            "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
            de->conn->request_info.uri, href, de->st.is_directory ? "/" : "",
            de->file_name, de->st.is_directory ? "/" : "", mod, size);
}

// This function is called from send_directory() and used for
// sorting directory entries by size, or name, or modification time.
// On windows, __cdecl specification is needed in case if project is built
// with __stdcall convention. qsort always requires __cdels callback.
static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
    const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
    const char *query_string = a->conn->request_info.query_string;
    int cmp_result = 0;

    if (query_string == NULL) {
        query_string = "na";
    }

    if (a->st.is_directory && !b->st.is_directory) {
        return -1;  // Always put directories on top
    } else if (!a->st.is_directory && b->st.is_directory) {
        return 1;   // Always put directories on top
    } else if (*query_string == 'n') {
        cmp_result = strcmp(a->file_name, b->file_name);
    } else if (*query_string == 's') {
        cmp_result = a->st.size == b->st.size ? 0 :
            a->st.size > b->st.size ? 1 : -1;
    } else if (*query_string == 'd') {
        cmp_result = a->st.mtime == b->st.mtime ? 0 :
            a->st.mtime > b->st.mtime ? 1 : -1;
    }

    return query_string[1] == 'd' ? -cmp_result : cmp_result;
}

static void handle_directory_request(struct mg_connection *conn,
        const char *dir) {
    struct dirent *dp;
    DIR *dirp;
    struct de *entries = NULL;
    char path[PATH_MAX];
    int i, sort_direction, num_entries = 0, arr_size = 128;

    if ((dirp = opendir(dir)) == NULL) {
        send_http_error(conn, 500, "Cannot open directory",
                "Error: opendir(%s): %s", path, strerror(ERRNO));
        return;
    }

    (void) mg_printf(conn, "%s",
            "HTTP/1.1 200 OK\r\n"
            "Connection: close\r\n"
            "Content-Type: text/html; charset=utf-8\r\n\r\n");

    sort_direction = conn->request_info.query_string != NULL &&
        conn->request_info.query_string[1] == 'd' ? 'a' : 'd';

    while ((dp = readdir(dirp)) != NULL) {

        // Do not show current dir and passwords file
        if (!strcmp(dp->d_name, ".") ||
                !strcmp(dp->d_name, "..") ||
                !strcmp(dp->d_name, PASSWORDS_FILE_NAME))
            continue;

        if (entries == NULL || num_entries >= arr_size) {
            arr_size *= 2;
            entries = (struct de *) realloc(entries,
                    arr_size * sizeof(entries[0]));
        }

        if (entries == NULL) {
            closedir(dirp);
            send_http_error(conn, 500, "Cannot open directory",
                    "%s", "Error: cannot allocate memory");
            return;
        }

        mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, DIRSEP, dp->d_name);

        // If we don't memset stat structure to zero, mtime will have
        // garbage and strftime() will segfault later on in
        // print_dir_entry(). memset is required only if mg_stat()
        // fails. For more details, see
        // http://code.google.com/p/mongoose/issues/detail?id=79
        if (mg_stat(path, &entries[num_entries].st) != 0) {
            memset(&entries[num_entries].st, 0, sizeof(entries[num_entries].st));
        }

        entries[num_entries].conn = conn;
        entries[num_entries].file_name = mg_strdup(dp->d_name);
        num_entries++;
    }
    (void) closedir(dirp);

    conn->num_bytes_sent += mg_printf(conn,
            "<html><head><title>Index of %s</title>"
            "<style>th {text-align: left;}</style></head>"
            "<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
            "<tr><th><a href=\"?n%c\">Name</a></th>"
            "<th><a href=\"?d%c\">Modified</a></th>"
            "<th><a href=\"?s%c\">Size</a></th></tr>"
            "<tr><td colspan=\"3\"><hr></td></tr>",
            conn->request_info.uri, conn->request_info.uri,
            sort_direction, sort_direction, sort_direction);

    // Print first entry - link to a parent directory
    conn->num_bytes_sent += mg_printf(conn,
            "<tr><td><a href=\"%s%s\">%s</a></td>"
            "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
            conn->request_info.uri, "..", "Parent directory", "-", "-");

    // Sort and print directory entries
    qsort(entries, (size_t)num_entries, sizeof(entries[0]), compare_dir_entries);
    for (i = 0; i < num_entries; i++) {
        print_dir_entry(&entries[i]);
        free(entries[i].file_name);
    }
    free(entries);

    conn->num_bytes_sent += mg_printf(conn, "%s", "</table></body></html>");
    conn->request_info.status_code = 200;
}

// Send len bytes from the opened file to the client.
static void send_file_data(struct mg_connection *conn, FILE *fp, int64_t len) {
    char buf[BUFSIZ];
    int to_read, num_read, num_written;

    while (len > 0) {
        // Calculate how much to read from the file in the buffer
        to_read = sizeof(buf);
        if ((int64_t) to_read > len)
            to_read = (int) len;

        // Read from file, exit the loop on error
        if ((num_read = fread(buf, 1, (size_t)to_read, fp)) == 0)
            break;

        // Send read bytes to the client, exit the loop on error
        if ((num_written = mg_write(conn, buf, (size_t)num_read)) != num_read)
            break;

        // Both read and were successful, adjust counters
        conn->num_bytes_sent += num_written;
        len -= num_written;
    }
}

static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
    return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
}

static void handle_file_request(struct mg_connection *conn, const char *path,
        struct mgstat *stp) {
    char date[64], lm[64], etag[64], range[64];
    const char *fmt = "%a, %d %b %Y %H:%M:%S %Z", *msg = "OK", *hdr;
    time_t curtime = time(NULL);
    int64_t cl, r1, r2;
    struct vec mime_vec;
    FILE *fp;
    int n;

    get_mime_type(conn->ctx, path, &mime_vec);
    cl = stp->size;
    conn->request_info.status_code = 200;
    range[0] = '\0';

    if ((fp = mg_fopen(path, "rb")) == NULL) {
        send_http_error(conn, 500, http_500_error,
                "fopen(%s): %s", path, strerror(ERRNO));
        return;
    }
    set_close_on_exec(fileno(fp));

    // If Range: header specified, act accordingly
    r1 = r2 = 0;
    hdr = mg_get_header(conn, "Range");
    if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0) {
        conn->request_info.status_code = 206;
        (void) fseeko(fp, (off_t) r1, SEEK_SET);
        cl = n == 2 ? r2 - r1 + 1: cl - r1;
        (void) mg_snprintf(conn, range, sizeof(range),
                "Content-Range: bytes "
                "%" INT64_FMT "-%"
                INT64_FMT "/%" INT64_FMT "\r\n",
                r1, r1 + cl - 1, stp->size);
        msg = "Partial Content";
    }

    // Prepare Etag, Date, Last-Modified headers
    (void) strftime(date, sizeof(date), fmt, localtime(&curtime));
    (void) strftime(lm, sizeof(lm), fmt, localtime(&stp->mtime));
    (void) mg_snprintf(conn, etag, sizeof(etag), "%lx.%lx",
            (unsigned long) stp->mtime, (unsigned long) stp->size);

    (void) mg_printf(conn,
            "HTTP/1.1 %d %s\r\n"
            "Date: %s\r\n"
            "Last-Modified: %s\r\n"
            "Etag: \"%s\"\r\n"
            "Content-Type: %.*s\r\n"
            "Content-Length: %" INT64_FMT "\r\n"
            "Connection: %s\r\n"
            "Accept-Ranges: bytes\r\n"
            "%s\r\n",
            conn->request_info.status_code, msg, date, lm, etag,
            mime_vec.len, mime_vec.ptr, cl, suggest_connection_header(conn), range);

    if (strcmp(conn->request_info.request_method, "HEAD") != 0) {
        send_file_data(conn, fp, cl);
    }
    (void) fclose(fp);
}

// Parse HTTP headers from the given buffer, advance buffer to the point
// where parsing stopped.
static void parse_http_headers(char **buf, struct mg_request_info *ri) {
    int i;

    for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
        ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);
        ri->http_headers[i].value = skip(buf, "\r\n");
        if (ri->http_headers[i].name[0] == '\0')
            break;
        ri->num_headers = i + 1;
    }
}

static int is_valid_http_method(const char *method) {
    return !strcmp(method, "GET") || !strcmp(method, "POST") ||
        !strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||
        !strcmp(method, "PUT") || !strcmp(method, "DELETE");
}

// Parse HTTP request, fill in mg_request_info structure.
static int parse_http_request(char *buf, struct mg_request_info *ri) {
    int status = 0;

    // RFC says that all initial whitespaces should be ingored
    while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
        buf++;
    }

    ri->request_method = skip(&buf, " ");
    ri->uri = skip(&buf, " ");
    ri->http_version = skip(&buf, "\r\n");

    if (is_valid_http_method(ri->request_method) &&
            strncmp(ri->http_version, "HTTP/", 5) == 0) {
        ri->http_version += 5;   /* Skip "HTTP/" */
        parse_http_headers(&buf, ri);
        status = 1;
    }

    return status;
}

// Keep reading the input (either opened file descriptor fd, or socket sock,
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
// buffer (which marks the end of HTTP request). Buffer buf may already
// have some data. The length of the data is stored in nread.
// Upon every read operation, increase nread by the number of bytes read.
static int read_request(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz,
        int *nread) {
    int n, request_len;

    request_len = 0;
    while (*nread < bufsiz && request_len == 0) {
        n = pull(fp, sock, ssl, buf + *nread, bufsiz - *nread);
        if (n <= 0) {
            break;
        } else {
            *nread += n;
            request_len = get_request_len(buf, *nread);
        }
    }

    return request_len;
}

// For given directory path, substitute it to valid index file.
// Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp.
static int substitute_index_file(struct mg_connection *conn, char *path,
        size_t path_len, struct mgstat *stp) {
    const char *list = conn->ctx->config[INDEX_FILES];
    struct mgstat st;
    struct vec filename_vec;
    size_t n = strlen(path);
    int found = 0;

    // The 'path' given to us points to the directory. Remove all trailing
    // directory separator characters from the end of the path, and
    // then append single directory separator character.
    while (n > 0 && IS_DIRSEP_CHAR(path[n - 1])) {
        n--;
    }
    path[n] = DIRSEP;

    // Traverse index files list. For each entry, append it to the given
    // path and see if the file exists. If it exists, break the loop
    while ((list = next_option(list, &filename_vec, NULL)) != NULL) {

        // Ignore too long entries that may overflow path buffer
        if (filename_vec.len > path_len - n)
            continue;

        // Prepare full path to the index file
        (void) mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1);

        // Does it exist?
        if (mg_stat(path, &st) == 0) {
            // Yes it does, break the loop
            *stp = st;
            found = 1;
            break;
        }
    }

    // If no index file exists, restore directory path
    if (!found) {
        path[n] = '\0';
    }

    return found;
}

// Return True if we should reply 304 Not Modified.
static int is_not_modified(const struct mg_connection *conn,
        const struct mgstat *stp) {
    const char *ims = mg_get_header(conn, "If-Modified-Since");
    return ims != NULL && stp->mtime <= parse_date_string(ims);
}

static int forward_body_data(struct mg_connection *conn, FILE *fp,
        SOCKET sock, SSL *ssl) {
    const char *expect, *buffered;
    char buf[BUFSIZ];
    int to_read, nread, buffered_len, success = 0;

    expect = mg_get_header(conn, "Expect");
    assert(fp != NULL);

    if (conn->content_len == -1) {
        send_http_error(conn, 411, "Length Required", "");
    } else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {
        send_http_error(conn, 417, "Expectation Failed", "");
    } else {
        if (expect != NULL) {
            (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n");
        }

        buffered = conn->buf + conn->request_len;
        buffered_len = conn->data_len - conn->request_len;
        assert(buffered_len >= 0);
        assert(conn->consumed_content == 0);

        if (buffered_len > 0) {
            if ((int64_t) buffered_len > conn->content_len) {
                buffered_len = (int) conn->content_len;
            }
            push(fp, sock, ssl, buffered, (int64_t) buffered_len);
            conn->consumed_content += buffered_len;
        }

        while (conn->consumed_content < conn->content_len) {
            to_read = sizeof(buf);
            if ((int64_t) to_read > conn->content_len - conn->consumed_content) {
                to_read = (int) (conn->content_len - conn->consumed_content);
            }
            nread = pull(NULL, conn->client.sock, conn->ssl, buf, to_read);
            if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {
                break;
            }
            conn->consumed_content += nread;
        }

        if (conn->consumed_content == conn->content_len) {
            success = 1;
        }

        // Each error code path in this function must send an error
        if (!success) {
            send_http_error(conn, 577, http_500_error, "");
        }
    }

    return success;
}

#if !defined(NO_CGI)
// This structure helps to create an environment for the spawned CGI program.
// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
// last element must be NULL.
// However, on Windows there is a requirement that all these VARIABLE=VALUE\0
// strings must reside in a contiguous buffer. The end of the buffer is
// marked by two '\0' characters.
// We satisfy both worlds: we create an envp array (which is vars), all
// entries are actually pointers inside buf.
struct cgi_env_block {
    struct mg_connection *conn;
    char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer
    int len; // Space taken
    char *vars[MAX_CGI_ENVIR_VARS]; // char **envp
    int nvars; // Number of variables
};

// Append VARIABLE=VALUE\0 string to the buffer, and add a respective
// pointer into the vars array.
static char *addenv(struct cgi_env_block *block, const char *fmt, ...) {
    int n, space;
    char *added;
    va_list ap;

    // Calculate how much space is left in the buffer
    space = sizeof(block->buf) - block->len - 2;
    assert(space >= 0);

    // Make a pointer to the free space int the buffer
    added = block->buf + block->len;

    // Copy VARIABLE=VALUE\0 string into the free space
    va_start(ap, fmt);
    n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap);
    va_end(ap);

    // Make sure we do not overflow buffer and the envp array
    if (n > 0 && n < space &&
            block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {
        // Append a pointer to the added string into the envp array
        block->vars[block->nvars++] = block->buf + block->len;
        // Bump up used length counter. Include \0 terminator
        block->len += n + 1;
    }

    return added;
}

static void prepare_cgi_environment(struct mg_connection *conn,
        const char *prog,
        struct cgi_env_block *blk) {
    const char *s, *slash;
    struct vec var_vec, root;
    char *p;
    int  i;

    blk->len = blk->nvars = 0;
    blk->conn = conn;

    get_document_root(conn, &root);

    addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);
    addenv(blk, "SERVER_ROOT=%.*s", root.len, root.ptr);
    addenv(blk, "DOCUMENT_ROOT=%.*s", root.len, root.ptr);

    // Prepare the environment block
    addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
    addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
    addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP
    addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.u.sin.sin_port));
    addenv(blk, "REQUEST_METHOD=%s", conn->request_info.request_method);
    addenv(blk, "REMOTE_ADDR=%s",
            inet_ntoa(conn->client.rsa.u.sin.sin_addr));
    addenv(blk, "REMOTE_PORT=%d", conn->request_info.remote_port);
    addenv(blk, "REQUEST_URI=%s", conn->request_info.uri);

    // SCRIPT_NAME
    assert(conn->request_info.uri[0] == '/');
    slash = strrchr(conn->request_info.uri, '/');
    if ((s = strrchr(prog, '/')) == NULL)
        s = prog;
    addenv(blk, "SCRIPT_NAME=%.*s%s", slash - conn->request_info.uri,
            conn->request_info.uri, s);

    addenv(blk, "SCRIPT_FILENAME=%s", prog);
    addenv(blk, "PATH_TRANSLATED=%s", prog);
    addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on");

    if ((s = mg_get_header(conn, "Content-Type")) != NULL)
        addenv(blk, "CONTENT_TYPE=%s", s);

    if (conn->request_info.query_string != NULL)
        addenv(blk, "QUERY_STRING=%s", conn->request_info.query_string);

    if ((s = mg_get_header(conn, "Content-Length")) != NULL)
        addenv(blk, "CONTENT_LENGTH=%s", s);

    if ((s = getenv("PATH")) != NULL)
        addenv(blk, "PATH=%s", s);

#if defined(_WIN32)
    if ((s = getenv("COMSPEC")) != NULL)
        addenv(blk, "COMSPEC=%s", s);
    if ((s = getenv("SYSTEMROOT")) != NULL)
        addenv(blk, "SYSTEMROOT=%s", s);
#else
    if ((s = getenv("LD_LIBRARY_PATH")) != NULL)
        addenv(blk, "LD_LIBRARY_PATH=%s", s);
#endif /* _WIN32 */

    if ((s = getenv("PERLLIB")) != NULL)
        addenv(blk, "PERLLIB=%s", s);

    if (conn->request_info.remote_user != NULL) {
        addenv(blk, "REMOTE_USER=%s", conn->request_info.remote_user);
        addenv(blk, "%s", "AUTH_TYPE=Digest");
    }

    // Add all headers as HTTP_* variables
    for (i = 0; i < conn->request_info.num_headers; i++) {
        p = addenv(blk, "HTTP_%s=%s",
                conn->request_info.http_headers[i].name,
                conn->request_info.http_headers[i].value);

        // Convert variable name into uppercase, and change - to _
        for (; *p != '=' && *p != '\0'; p++) {
            if (*p == '-')
                *p = '_';
            *p = (char) toupper(* (unsigned char *) p);
        }
    }

    // Add user-specified variables
    s = conn->ctx->config[CGI_ENVIRONMENT];
    while ((s = next_option(s, &var_vec, NULL)) != NULL) {
        addenv(blk, "%.*s", var_vec.len, var_vec.ptr);
    }

    blk->vars[blk->nvars++] = NULL;
    blk->buf[blk->len++] = '\0';

    assert(blk->nvars < (int) ARRAY_SIZE(blk->vars));
    assert(blk->len > 0);
    assert(blk->len < (int) sizeof(blk->buf));
}

static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
    int headers_len, data_len, i, fd_stdin[2], fd_stdout[2];
    const char *status;
    char buf[BUFSIZ], *pbuf, dir[PATH_MAX], *p;
    struct mg_request_info ri;
    struct cgi_env_block blk;
    FILE *in, *out;
    pid_t pid;

    prepare_cgi_environment(conn, prog, &blk);

    // CGI must be executed in its own directory. 'dir' must point to the
    // directory containing executable program, 'p' must point to the
    // executable program name relative to 'dir'.
    (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog);
    if ((p = strrchr(dir, DIRSEP)) != NULL) {
        *p++ = '\0';
    } else {
        dir[0] = '.', dir[1] = '\0';
        p = (char *) prog;
    }

    pid = (pid_t) -1;
    fd_stdin[0] = fd_stdin[1] = fd_stdout[0] = fd_stdout[1] = -1;
    in = out = NULL;

    if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) {
        send_http_error(conn, 500, http_500_error,
                "Cannot create CGI pipe: %s", strerror(ERRNO));
        goto done;
    } else if ((pid = spawn_process(conn, p, blk.buf, blk.vars,
                    fd_stdin[0], fd_stdout[1], dir)) == (pid_t) -1) {
        goto done;
    } else if ((in = fdopen(fd_stdin[1], "wb")) == NULL ||
            (out = fdopen(fd_stdout[0], "rb")) == NULL) {
        send_http_error(conn, 500, http_500_error,
                "fopen: %s", strerror(ERRNO));
        goto done;
    }

    setbuf(in, NULL);
    setbuf(out, NULL);

    // spawn_process() must close those!
    // If we don't mark them as closed, close() attempt before
    // return from this function throws an exception on Windows.
    // Windows does not like when closed descriptor is closed again.
    fd_stdin[0] = fd_stdout[1] = -1;

    // Send POST data to the CGI process if needed
    if (!strcmp(conn->request_info.request_method, "POST") &&
            !forward_body_data(conn, in, INVALID_SOCKET, NULL)) {
        goto done;
    }

    // Now read CGI reply into a buffer. We need to set correct
    // status code, thus we need to see all HTTP headers first.
    // Do not send anything back to client, until we buffer in all
    // HTTP headers.
    data_len = 0;
    headers_len = read_request(out, INVALID_SOCKET, NULL,
            buf, sizeof(buf), &data_len);
    if (headers_len <= 0) {
        send_http_error(conn, 500, http_500_error,
                "CGI program sent malformed HTTP headers: [%.*s]",
                data_len, buf);
        goto done;
    }
    pbuf = buf;
    buf[headers_len - 1] = '\0';
    parse_http_headers(&pbuf, &ri);

    // Make up and send the status line
    status = get_header(&ri, "Status");
    conn->request_info.status_code = status == NULL ? 200 : atoi(status);
    (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n", conn->request_info.status_code);

    // Send headers
    for (i = 0; i < ri.num_headers; i++) {
        mg_printf(conn, "%s: %s\r\n",
                ri.http_headers[i].name, ri.http_headers[i].value);
    }
    (void) mg_write(conn, "\r\n", 2);

    // Send chunk of data that may be read after the headers
    conn->num_bytes_sent += mg_write(conn, buf + headers_len,
            (size_t)(data_len - headers_len));

    // Read the rest of CGI output and send to the client
    send_file_data(conn, out, INT64_MAX);

done:
    if (pid != (pid_t) -1) {
        kill(pid, SIGKILL);
#if !defined(_WIN32)
        do {} while (waitpid(-1, &i, WNOHANG) > 0);
#endif
    }
    if (fd_stdin[0] != -1) {
        (void) close(fd_stdin[0]);
    }
    if (fd_stdout[1] != -1) {
        (void) close(fd_stdout[1]);
    }

    if (in != NULL) {
        (void) fclose(in);
    } else if (fd_stdin[1] != -1) {
        (void) close(fd_stdin[1]);
    }

    if (out != NULL) {
        (void) fclose(out);
    } else if (fd_stdout[0] != -1) {
        (void) close(fd_stdout[0]);
    }
}
#endif // !NO_CGI

